package com.dobbinsoft.iotechn.device.service.open;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dobbinsoft.iotechn.core.constant.Const;
import com.dobbinsoft.iotechn.core.exception.CoreExceptionDefinition;
import com.dobbinsoft.iotechn.core.exception.ServiceException;
import com.dobbinsoft.iotechn.data.annotaion.AspectCommonCache;
import com.dobbinsoft.iotechn.device.api.open.ControllerDevService;
import com.dobbinsoft.iotechn.device.constant.DeviceConst;
import com.dobbinsoft.iotechn.device.domain.*;
import com.dobbinsoft.iotechn.device.dto.ControllerComponentAttrDTO;
import com.dobbinsoft.iotechn.device.dto.ControllerComponentAttrRequestDTO;
import com.dobbinsoft.iotechn.device.dto.ControllerComponentDTO;
import com.dobbinsoft.iotechn.device.exception.DeviceExceptionDefinition;
import com.dobbinsoft.iotechn.device.exception.DeviceServiceException;
import com.dobbinsoft.iotechn.device.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Description:
 * User: rize
 * Date: 2020/6/18
 * Time: 17:07
 */
@Service("controllerDevService")
public class ControllerDevServiceImpl implements ControllerDevService {

    @Autowired
    private ProductControllerMapper productControllerMapper;

    @Autowired
    private ControllerComponentLibMapper controllerComponentLibMapper;

    @Autowired
    private ControllerComponentLibAttrMapper controllerComponentLibAttrMapper;

    @Autowired
    private ControllerComponentMapper controllerComponentMapper;

    @Autowired
    private ControllerComponentAttrMapper controllerComponentAttrMapper;

    @Autowired
    private ProductMapper productMapper;


    @Override
    public List<ProductControllerDO> list(Long productId, Long developerId) throws ServiceException {
        this.checkProductOwner(productId, developerId);
        return productControllerMapper.selectList(new QueryWrapper<ProductControllerDO>().eq("product_id", productId));
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public String create(String title, String description, Long productId, Long developerId) throws ServiceException {
        this.checkProductOwner(productId, developerId);
        Integer count = productControllerMapper.selectCount(new QueryWrapper<ProductControllerDO>().eq("product_id", productId));
        if (count >= 10) {
            throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_NUM_MUST_LT_TEN);
        }
        ProductControllerDO controllerDO = new ProductControllerDO();
        controllerDO.setProductId(productId);
        controllerDO.setTitle(title);
        controllerDO.setDescription(description);
        controllerDO.setDeveloperId(developerId);
        Date now = new Date();
        controllerDO.setGmtUpdate(now);
        controllerDO.setGmtCreate(now);
        if (productControllerMapper.insert(controllerDO) > 0) {
            return "ok";
        }
        throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_UNKNOWN_EXCEPTION);
    }

    @Override
    @DS(Const.DB_S)
    public List<ControllerComponentDTO> listComponent(Long controllerId, Long developerId) throws ServiceException {
        List<ControllerComponentDTO> controllerComponentList = controllerComponentMapper.getControllerComponent(controllerId);
        return controllerComponentList;
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public String createComponent(Long controllerId, Long componentLibId, Integer styleMode, String style, String title, Integer sort, String attrListJson, Long developerId) throws ServiceException {
        if (!productControllerMapper.checkOwner(controllerId, developerId)) {
            throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_NOT_EXIST);
        }
        Date now = new Date();
        ControllerComponentDO controllerComponentDO = new ControllerComponentDO();
        // 若不存在，页面不会渲染
        controllerComponentDO.setComponentLibId(componentLibId);
        controllerComponentDO.setTitle(title);
        controllerComponentDO.setSort(sort);
        controllerComponentDO.setControllerId(controllerId);
        controllerComponentDO.setDeveloperId(developerId);
        controllerComponentDO.setStyleMode(styleMode);
        controllerComponentDO.setStyle(style);
        controllerComponentDO.setGmtCreate(now);
        controllerComponentDO.setGmtUpdate(now);
        if (controllerComponentMapper.insert(controllerComponentDO) > 0) {
            // 从外部传入的值
            List<ControllerComponentAttrRequestDTO> attrRequestDTOList = JSONObject.parseArray(attrListJson, ControllerComponentAttrRequestDTO.class);
            // 从数据库读取的值
            List<ControllerComponentLibAttrDO> attrDOList =
                    controllerComponentLibAttrMapper
                            .selectList(new QueryWrapper<ControllerComponentLibAttrDO>().eq("component_lib_id", componentLibId));
            // 校验前后一致性，防止恶意乱传
            this.checkDataConsistency(attrRequestDTOList, attrDOList);
            // 属性表 原型
            ControllerComponentAttrDO attrDO = new ControllerComponentAttrDO();
            attrDO.setDeveloperId(developerId);
            // 开发者Id
            // 标识是哪个具体的组件的属性
            attrDO.setComponentId(controllerComponentDO.getId());
            // 标识输入哪个控制器，也是冗余字段。这个冗余字段，可以用于前端查遥控器时，直接将其组件和组件属性一并查出来，再在前端分组，这样可减少DB的压力
            attrDO.setControllerId(controllerId);
            for (ControllerComponentAttrRequestDTO requestDTO : attrRequestDTOList) {
                try {

                    ControllerComponentAttrDO insertDO = attrDO.clone();
                    // 标识是哪个具体的属性
                    insertDO.setComponentLibAttrId(requestDTO.getLibAttrId());
                    insertDO.setValue(requestDTO.getValue());
                    insertDO.setGmtUpdate(now);
                    insertDO.setGmtCreate(now);
                    // 将其插入
                    controllerComponentAttrMapper.insert(insertDO);
                } catch (CloneNotSupportedException e) {
                }
            }
            return "ok";
        }
        throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_UNKNOWN_EXCEPTION);
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public String editComponent(Long componentId, Integer styleMode, String style, String title, Integer sort, String attrListStr, Long developerId) throws ServiceException {
        ControllerComponentDO controllerComponentDO = controllerComponentMapper
                .selectOne(new QueryWrapper<ControllerComponentDO>()
                        .eq("id", componentId)
                        .eq("developer_id", developerId));
        if (controllerComponentDO == null) {
            throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_COMPONENT_NOT_EXIST);
        }
        // 1. 更新主表
        ControllerComponentDO updateComponentDO = new ControllerComponentDO();
        updateComponentDO.setStyleMode(styleMode);
        updateComponentDO.setStyle(style);
        updateComponentDO.setId(componentId);
        updateComponentDO.setTitle(title);
        updateComponentDO.setSort(sort);
        Date now = new Date();
        updateComponentDO.setGmtUpdate(now);
        if (controllerComponentMapper.updateById(updateComponentDO) > 0) {
            // 2. 更新属性
            // 从外部传入的值
            List<ControllerComponentAttrRequestDTO> attrRequestDTOList = JSONObject.parseArray(attrListStr, ControllerComponentAttrRequestDTO.class);
            // 从数据库读取的值
            List<ControllerComponentLibAttrDO> attrDOList =
                    controllerComponentLibAttrMapper
                            .selectList(new QueryWrapper<ControllerComponentLibAttrDO>().eq("component_lib_id", controllerComponentDO.getComponentLibId()));
            // 校验前后一致性，防止恶意乱传
            this.checkDataConsistency(attrRequestDTOList, attrDOList);
            for (ControllerComponentAttrRequestDTO requestDTO : attrRequestDTOList) {
                // 差量更新。对于属性，只能更新Value
                ControllerComponentAttrDO updateAttrDO = new ControllerComponentAttrDO();
                updateAttrDO.setValue(requestDTO.getValue());
                updateAttrDO.setGmtUpdate(now);
                int update = controllerComponentAttrMapper.update(updateAttrDO,
                        new QueryWrapper<ControllerComponentAttrDO>()
                                .eq("component_id", componentId)
                                .eq("component_lib_attr_id", requestDTO.getLibAttrId()));
                if (update == 0) {
                    // 更新失败。存在情况，这个属性是组件创建后，新添加的。
                    ControllerComponentAttrDO insertAttrDO = new ControllerComponentAttrDO();
                    // 开发者Id
                    insertAttrDO.setDeveloperId(developerId);
                    // 标识是哪个具体的组件的属性
                    insertAttrDO.setComponentId(controllerComponentDO.getId());
                    // 标识输入哪个控制器，也是冗余字段。这个冗余字段，可以用于前端查遥控器时，直接将其组件和组件属性一并查出来，再在前端分组，这样可减少DB的压力
                    insertAttrDO.setControllerId(controllerComponentDO.getControllerId());
                    insertAttrDO.setComponentLibAttrId(requestDTO.getLibAttrId());
                    insertAttrDO.setValue(requestDTO.getValue());
                    insertAttrDO.setGmtUpdate(now);
                    insertAttrDO.setGmtCreate(now);
                    // 将其插入
                    controllerComponentAttrMapper.insert(insertAttrDO);
                }
            }
            return "ok";
        }
        throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_UNKNOWN_EXCEPTION);
    }

    @Override
    @DS((Const.DB_M))
    @Transactional(rollbackFor = Exception.class)
    public String deleteComponent(Long componentId, Long developerId) throws ServiceException {
        // 1. 删除主表
        if (controllerComponentMapper.delete(
                new QueryWrapper<ControllerComponentDO>()
                        .eq("id", componentId)
                        .eq("developer_id", developerId)) > 0) {
            // 删除属性表
            controllerComponentAttrMapper.delete(new QueryWrapper<ControllerComponentAttrDO>().eq("component_id", componentId));
            return "ok";
        }
        throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_COMPONENT_NOT_EXIST);
    }

    private void checkDataConsistency(List<ControllerComponentAttrRequestDTO> attrRequestDTOList, List<ControllerComponentLibAttrDO> attrDOList) throws ServiceException {
        // 比对前端穿回来的数据和数据库中的数据是不是一致的，并且为空值，若是存在默认值的属性赋值上默认值
        Set<Long> s1 = attrRequestDTOList.stream().map(item -> item.getLibAttrId()).collect(Collectors.toSet());
        Set<Long> s2 = attrDOList.stream().map(item -> item.getId()).collect(Collectors.toSet());
        if (s1.size() != s2.size() || !s1.containsAll(s2)) {
            throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_COMPONENT_ATTR_VALUE_NOT_CONSISTENCY);
        }
        Map<Long, List<ControllerComponentLibAttrDO>> dataMap = attrDOList.stream().collect(Collectors.groupingBy(ControllerComponentLibAttrDO::getId));
        for (ControllerComponentAttrRequestDTO requestDTO : attrRequestDTOList) {
            String value = requestDTO.getValue();
            if (StringUtils.isEmpty(value)) {
                // 若有空值，则将
                ControllerComponentLibAttrDO libAttrDO = dataMap.get(requestDTO.getLibAttrId()).get(0);
                if (!StringUtils.isEmpty(libAttrDO.getDefValue())) {
                    // 若默认值存在，则赋值上默认值
                    requestDTO.setValue(libAttrDO.getDefValue());
                } else if (libAttrDO.getNullable().intValue() == 0) {
                    // 若不能为空，则直接抛出异常
                    throw new DeviceServiceException(CoreExceptionDefinition
                            .buildVariableException(DeviceExceptionDefinition.DEVICE_PRODUCT_CONTROLLER_COMPONENT_ATTR_VALUE_CANNOT_BE_EMPTY, libAttrDO.getTitle()));
                }
            }
        }
    }

    @Override
    @DS(Const.DB_M)
    @Transactional(rollbackFor = Exception.class)
    public String adjustSort(String sortMapStr, Long developerId) throws ServiceException {
        Map<Long, Integer> sortMap = JSONObject.parseObject(sortMapStr, new TypeReference<Map<Long, Integer>>() {
        });
        sortMap.forEach((k, v) -> {
            controllerComponentMapper.updateSort(k, v, developerId);
        });
        return "ok";
    }

    @Override
    @DS(Const.DB_S)
    public List<ControllerComponentAttrDO> listComponentAttr(Long componentId, Long developerId) throws ServiceException {
        List<ControllerComponentAttrDO> attrList = controllerComponentAttrMapper.selectList(new QueryWrapper<ControllerComponentAttrDO>().eq("component_id", componentId));
        return attrList;
    }


    @Override
    @DS(Const.DB_S)
//    @AspectCommonCache(value = DeviceConst.CA_CONTROLLER_COMPONENT, second = 60 * 5)
    public List<ControllerComponentLibDO> listLib(Long developerId) throws ServiceException {
        return controllerComponentLibMapper.selectList(null);
    }

    @Override
    @DS(Const.DB_S)
//    @AspectCommonCache(value = DeviceConst.CA_CONTROLLER_COMPONENT_ATTR, argIndex = 0, second = 60 * 5)
    public List<ControllerComponentLibAttrDO> listLibAttr(Long libId, Long developerId) throws ServiceException {
        return controllerComponentLibAttrMapper.selectList(new QueryWrapper<ControllerComponentLibAttrDO>().eq("component_lib_id", libId));
    }


    /**
     * 检查产品是否属于当前用户
     *
     * @param productId
     * @param developerId
     * @throws ServiceException
     */
    private void checkProductOwner(Long productId, Long developerId) throws ServiceException {
        Integer count = productMapper.selectCount(
                new QueryWrapper<ProductDO>()
                        .eq("id", productId)
                        .eq("developer_id", developerId));
        if (count == 0) {
            throw new DeviceServiceException(DeviceExceptionDefinition.DEVICE_PRODUCT_NOT_EXIST);
        }
    }

}
